/*
 * ftcp_server.c
 *
 *  Created on: Mar 21, 2021
 *      Author: kychu
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <stdint.h>
#include <dirent.h>
#include <sys/param.h>
#include <sys/stat.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_err.h"
#include "esp_vfs.h"
#include "nvs_flash.h"
#include "esp_netif.h"

#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include <lwip/netdb.h>

#define PORT 8086

#define FILE_CACHE_SIZE 1024

static uint8_t *tcp_rx_buff = NULL;
static char mount_path[32];

static const char *TAG = "ftcp";

static int prepare_dirs(char *fullpath)
{
  int index = 0, ret = 0;
  int path_len = strlen(fullpath) - 1;
//  printf("path_len:%d\n", path_len);
  do {
    if(fullpath[path_len] == '/') break;
    if(path_len == 0) break;
    path_len --;
  } while(1);
//  while(fullpath[-- path_len] != '/' && path_len > 0);
  char *tar_path = calloc(1, path_len + 1); // reserve tail of '\0'
  strlcpy(tar_path, fullpath, path_len + 1);
//  printf("path_len:%d\n", path_len);
  printf("file:%s\n", fullpath);
  printf("file directory:%s\n", tar_path);
  if(!strcmp(tar_path, mount_path)) {
    free(tar_path);
    return 0;
  }
  char *dir_path = calloc(1, path_len + 1);
  strlcpy(dir_path, tar_path, path_len + 1);

retry:
  if(!strcmp(dir_path, mount_path)) {
    printf("ERROR:same to mount path.\n");
    return -1;
  }
  DIR *dir = opendir(dir_path);
  if (!dir) {
	ESP_LOGW(TAG, "opendir %s failed, try to create it!", dir_path);
	if(mkdir(dir_path, 0755) == -1) {
	  ESP_LOGE(TAG, "mkdir %s failed!", dir_path);
      index = strlen(dir_path) - 1;
      if(index < 0) { ret = -1; goto exit; }
      while(dir_path[index --] != '/' && index >= 0);
      dir_path[index + 1] = 0; // cut string
      goto retry;
	} else {
      if(!strcmp(dir_path, tar_path)) { goto exit; }
      strlcpy(dir_path, tar_path, path_len + 1);
      goto retry;
	}
  } else {
	closedir(dir);
  }
exit:
  free(tar_path);
  free(dir_path);
  return ret;
}
static int file_write(const int sock, char *path, size_t size)
{
  int count = 0, ret = 0;
  FILE *fd = NULL;
  int len = strlen(path) + strlen(mount_path) + 2;
  char *file = calloc(1, len);
  if(file == NULL) {
    ESP_LOGE(TAG, "no memory!");
    return -1;
  }
  strlcpy(file, mount_path, len);
  if(path[0] != '/') strlcat(file, "/", len);
  strlcat(file, path, len);
  if(prepare_dirs(file) < 0) {
    ESP_LOGE(TAG, "directory error");
    ret = -1;
    goto exit;
  }
  fd = fopen(file, "w");
  if(!fd) {
    ESP_LOGE(TAG, "failed to open file %s", file);
    ret = -1; goto exit;
  }
  if(send(sock, "OK!", 3, 0) < 0) {
    ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
    ret = -1;
    goto exit;
  }
if(size > 0) {
  do {
    len = recv(sock, tcp_rx_buff, FILE_CACHE_SIZE, 0);
    if(len < 0) {
      ESP_LOGE(TAG, "Error occurred during receiving: errno %d", errno);
      ret = -1; goto exit;
    } else if(len == 0) {
      ESP_LOGW(TAG, "Connection closed");
      ret = -1; goto exit;
    } else {
      if(fwrite(tcp_rx_buff, 1, len, fd) != len) {
        ESP_LOGE(TAG, "failed to write file");
        ret = -1; goto exit;
      }
      count += len;
    }
  } while(count < size);

  if(send(sock, "OK!", 3, 0) < 0) {
    ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
    ret = -1;
  }
}
exit:
  free(file);
  if(fd != NULL) fclose(fd);
  return ret;
}

int up_notification(const int sock, const char *cmd, const char *desc)
{
  int len = strlen(cmd) + strlen(desc) + 3;
  char *cache = calloc(1, len);
  strlcpy(cache, cmd, len);
  strlcat(cache, ", ", len);
  strlcat(cache, desc, len);
  if(send(sock, cache, strlen(cache), 0) < 0) { // try to notify host
    ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
    return -1;
  }
  return 0;
}

int tcp_upgrade(const int sock, size_t size, uint8_t *buff, size_t cache_len);
static void do_retransmit(const int sock)
{
  int len;
do{
  memset(tcp_rx_buff, 0, FILE_CACHE_SIZE);
  len = recv(sock, tcp_rx_buff, FILE_CACHE_SIZE, 0);
  if (len < 0) {
    ESP_LOGE(TAG, "Error occurred during receiving: errno %d", errno);
    return;
  } else if (len == 0) {
    ESP_LOGW(TAG, "Connection closed");
    return;
  } else {
//    printf("recv:%s\n", tcp_rx_buff);
    char *ptr = (char *)tcp_rx_buff;
    int colon = 0;
    while(ptr[colon] != ':' && ptr[colon ++] != 0);
    ptr[colon] = 0;
    if(!strcmp(ptr, "write")) {
      ptr += colon + 1;
      if(ptr[0] != 0 && ptr[0] != ':') {
        colon = 0;
        while(ptr[colon] != ':' && ptr[colon ++] != 0);
        ptr[colon] = 0;
        size_t size;
        if(ptr[colon+1] != 0 && (sscanf(ptr+colon+1, "%d", &size) > 0)) {
          printf("write file:%s,size:%d\n", ptr, size);
          file_write(sock, ptr, size);
        } else {
          ESP_LOGE(TAG, "BAD write file LEN");
          up_notification(sock, "ERROR!", "file LEN error");
        }
      } else {
        ESP_LOGE(TAG, "BAD write request");
        up_notification(sock, "ERROR!", "BAD write request");
      }
    } else if(!strcmp(ptr, "upgrade")) {
      ptr += colon + 1;
      if(ptr[0] != 0 && ptr[0] != ':') {
        colon = 0;
        while(ptr[colon] != ':' && ptr[colon ++] != 0);
        ptr[colon] = 0;
        size_t size;
        if(ptr[colon+1] != 0 && (sscanf(ptr+colon+1, "%d", &size) > 0)) {
          printf("write file:%s,size:%d\n", ptr, size);
          tcp_upgrade(sock, size, tcp_rx_buff, FILE_CACHE_SIZE);
        } else {
          ESP_LOGE(TAG, "BAD firmware LEN");
          up_notification(sock, "ERROR!", "firmware LEN error");
        }
      } else {
        ESP_LOGE(TAG, "BAD upgrade request");
        up_notification(sock, "ERROR!", "BAD upgrade request");
      }
    } else if(!strcmp(ptr, "delete")) {
      printf("delete file\n");
    } else {
      printf("unknown CMD\n");
    }
  }
}while(1);
}

void ftcp_server_task(void *pvParameters)
{
  char addr_str[128];
  int ip_protocol = 0;
  struct sockaddr_in6 dest_addr;

  struct sockaddr_in *dest_addr_ip4 = (struct sockaddr_in *)&dest_addr;
  dest_addr_ip4->sin_addr.s_addr = htonl(INADDR_ANY);
  dest_addr_ip4->sin_family = AF_INET;
  dest_addr_ip4->sin_port = htons(PORT);
  ip_protocol = IPPROTO_IP;

  int listen_sock = socket(AF_INET, SOCK_STREAM, ip_protocol);
  if (listen_sock < 0) {
    ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
    vTaskDelete(NULL);
    return;
  }

  ESP_LOGI(TAG, "Socket created");

  int err = bind(listen_sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
  if (err != 0) {
    ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
    ESP_LOGE(TAG, "IPPROTO: %d", AF_INET);
    goto CLEAN_UP;
  }
  ESP_LOGI(TAG, "Socket bound, port %d", PORT);

  err = listen(listen_sock, 1);
  if (err != 0) {
    ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno);
    goto CLEAN_UP;
  }

  tcp_rx_buff = calloc(1, FILE_CACHE_SIZE);
  if(tcp_rx_buff == NULL) {
    ESP_LOGE(TAG, "no memory for tcp server!");
    goto CLEAN_UP;
  }
  memset(mount_path, 0, 32);
  strlcpy(mount_path, pvParameters, 32);

  while (1) {
    ESP_LOGI(TAG, "Socket listening");

    struct sockaddr_storage source_addr; // Large enough for both IPv4 or IPv6
    socklen_t addr_len = sizeof(source_addr);
    int sock = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len);
    if (sock < 0) {
      ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno);
      break;
    }

    // Convert ip address to string
    if (source_addr.ss_family == PF_INET) {
      inet_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr, addr_str, sizeof(addr_str) - 1);
    } else if (source_addr.ss_family == PF_INET6) {
      inet6_ntoa_r(((struct sockaddr_in6 *)&source_addr)->sin6_addr, addr_str, sizeof(addr_str) - 1);
    }
    ESP_LOGI(TAG, "Socket accepted ip address: %s", addr_str);

    do_retransmit(sock);

    shutdown(sock, 0);
    close(sock);
  }

CLEAN_UP:
  if(tcp_rx_buff != NULL) {
    free(tcp_rx_buff);
    tcp_rx_buff = NULL;
  }
  close(listen_sock);
  vTaskDelete(NULL);
}
